// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fuchsia/hardware/input/c/fidl.h>
#include <hid/hid.h>
#include <hid/usages.h>
#include <lib/fzl/fdio.h>
#include <zircon/syscalls.h>

#include <port/port.h>

#include <utility>

#include "keyboard.h"

#define LOW_REPEAT_KEY_FREQ 250000000
#define HIGH_REPEAT_KEY_FREQ 50000000

static int modifiers_from_keycode(uint8_t keycode) {
  switch (keycode) {
    case HID_USAGE_KEY_LEFT_SHIFT:
      return MOD_LSHIFT;
    case HID_USAGE_KEY_RIGHT_SHIFT:
      return MOD_RSHIFT;
    case HID_USAGE_KEY_LEFT_ALT:
      return MOD_LALT;
    case HID_USAGE_KEY_RIGHT_ALT:
      return MOD_RALT;
    case HID_USAGE_KEY_LEFT_CTRL:
      return MOD_LCTRL;
    case HID_USAGE_KEY_RIGHT_CTRL:
      return MOD_RCTRL;
  }
  return 0;
}

static void set_caps_lock_led(int keyboard_fd, bool caps_lock) {
  // The following bit to set is specified in "Device Class Definition
  // for Human Interface Devices (HID)", Version 1.11,
  // http://www.usb.org/developers/hidpage/HID1_11.pdf.  Zircon leaves
  // USB keyboards in boot mode, so the relevant section is Appendix B,
  // "Boot Interface Descriptors", "B.1 Protocol 1 (Keyboard)".
  const uint8_t kUsbCapsLockBit = 1 << 1;
  const uint8_t report_body[1] = {static_cast<uint8_t>(caps_lock ? kUsbCapsLockBit : 0)};

  // Temporarily wrap keyboard_fd, we will release it after the call so we don't close it.
  fzl::FdioCaller caller{fbl::unique_fd(keyboard_fd)};
  zx_status_t call_status;
  zx_status_t status = fuchsia_hardware_input_DeviceSetReport(
      caller.borrow_channel(), fuchsia_hardware_input_ReportType_OUTPUT, 0, report_body,
      sizeof(report_body), &call_status);
  caller.release().release();
  if (status != ZX_OK || call_status != ZX_OK) {
#if !BUILD_FOR_TEST
    printf("fuchsia.hardware.input.Device.SetReport() failed (returned %d, %d)\n", status,
           call_status);
#endif
  }
}

struct vc_input {
  port_fd_handler_t fh;
  port_handler_t th;
  zx_handle_t timer;

  keypress_handler_t handler;
  int fd;

  uint8_t previous_report_buf[8];
  uint8_t report_buf[8];
  hid_keys_t state[2];
  int cur_idx;
  int prev_idx;
  int modifiers;
  uint64_t repeat_interval;
  bool repeat_enabled;
};

// returns true if key was pressed and none were released
bool vc_input_process(vc_input_t* vi, uint8_t report[8]) {
  bool do_repeat = false;

  // process the key
  uint8_t keycode;
  hid_keys_t keys;

  hid_kbd_parse_report(report, &vi->state[vi->cur_idx]);

  hid_kbd_pressed_keys(&vi->state[vi->prev_idx], &vi->state[vi->cur_idx], &keys);
  hid_for_every_key(&keys, keycode) {
    if (keycode == HID_USAGE_KEY_ERROR_ROLLOVER) {
      return false;
    }
    vi->modifiers |= modifiers_from_keycode(keycode);
    if (keycode == HID_USAGE_KEY_CAPSLOCK) {
      vi->modifiers ^= MOD_CAPSLOCK;
      set_caps_lock_led(vi->fd, vi->modifiers & MOD_CAPSLOCK);
    }
    vi->handler(keycode, vi->modifiers);
    do_repeat = true;
  }

  hid_kbd_released_keys(&vi->state[vi->prev_idx], &vi->state[vi->cur_idx], &keys);
  hid_for_every_key(&keys, keycode) {
    vi->modifiers &= ~modifiers_from_keycode(keycode);
    do_repeat = false;
  }

  // swap key states
  vi->cur_idx = 1 - vi->cur_idx;
  vi->prev_idx = 1 - vi->prev_idx;

  return do_repeat;
}

#if !BUILD_FOR_TEST
static void vc_input_destroy(vc_input_t* vi) {
  port_cancel(&port, &vi->th);
  if (vi->fd >= 0) {
    port_fd_handler_done(&vi->fh);
    close(vi->fd);
  }
  zx_handle_close(vi->timer);
  free(vi);
}

static zx_status_t vc_timer_cb(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
  vc_input_t* vi = containerof(ph, vc_input_t, th);

  vc_input_process(vi, vi->previous_report_buf);
  vc_input_process(vi, vi->report_buf);

  // increase repeat rate if we're not yet at the fastest rate
  if ((vi->repeat_interval = vi->repeat_interval * 3 / 4) < HIGH_REPEAT_KEY_FREQ) {
    vi->repeat_interval = HIGH_REPEAT_KEY_FREQ;
  }

  zx_timer_set(vi->timer, zx_deadline_after(vi->repeat_interval), 0);

  return ZX_OK;
}

static zx_status_t vc_input_cb(port_fd_handler_t* fh, unsigned pollevt, uint32_t evt) {
  vc_input_t* vi = containerof(fh, vc_input_t, fh);
  ssize_t r;

  if (!(pollevt & POLLIN)) {
    r = ZX_ERR_PEER_CLOSED;
  } else {
    memcpy(vi->previous_report_buf, vi->report_buf, sizeof(vi->report_buf));
    r = read(vi->fd, vi->report_buf, sizeof(vi->report_buf));
  }
  if (r <= 0) {
    vc_input_destroy(vi);
    return ZX_ERR_STOP;
  }
  if ((size_t)(r) != sizeof(vi->report_buf)) {
    vi->repeat_interval = ZX_TIME_INFINITE;
    return ZX_OK;
  }

  if (vc_input_process(vi, vi->report_buf) && vi->repeat_enabled) {
    vi->repeat_interval = LOW_REPEAT_KEY_FREQ;
    zx_timer_set(vi->timer, zx_deadline_after(vi->repeat_interval), 0);
  } else {
    zx_timer_cancel(vi->timer);
  }
  return ZX_OK;
}
#endif

zx_status_t vc_input_create(vc_input_t** out, keypress_handler_t handler, int fd) {
  vc_input_t* vi = reinterpret_cast<vc_input_t*>(calloc(1, sizeof(vc_input_t)));
  if (vi == NULL) {
    return ZX_ERR_NO_MEMORY;
  }

  vi->fd = fd;
  vi->handler = handler;

  vi->cur_idx = 0;
  vi->prev_idx = 1;
  vi->modifiers = 0;
  vi->repeat_interval = ZX_TIME_INFINITE;
  vi->repeat_enabled = true;

  char* flag = getenv("virtcon.keyrepeat");
  if (flag && (!strcmp(flag, "0") || !strcmp(flag, "false"))) {
    printf("vc: Key repeat disabled\n");
    vi->repeat_enabled = false;
  }

#if !BUILD_FOR_TEST
  zx_status_t r;
  if ((r = zx_timer_create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &vi->timer)) < 0) {
    free(vi);
    return r;
  }

  vi->fh.func = vc_input_cb;
  if ((r = port_fd_handler_init(&vi->fh, fd, POLLIN | POLLHUP | POLLRDHUP)) < 0) {
    zx_handle_close(vi->timer);
    free(vi);
    return r;
  }

  if ((r = port_wait(&port, &vi->fh.ph)) < 0) {
    port_fd_handler_done(&vi->fh);
    zx_handle_close(vi->timer);
    free(vi);
    return r;
  }

  vi->th.handle = vi->timer;
  vi->th.waitfor = ZX_TIMER_SIGNALED;
  vi->th.func = vc_timer_cb;
  port_wait(&port, &vi->th);
#endif

  *out = vi;
  return ZX_OK;
}

#if !BUILD_FOR_TEST
zx_status_t new_input_device(int fd, keypress_handler_t handler) {
  // test to see if this is a device we can read
  uint32_t proto = fuchsia_hardware_input_BootProtocol_NONE;

  // Temporarily wrap fd, we will release it after the call so we don't close it.
  fzl::FdioCaller caller{fbl::unique_fd(fd)};
  zx_status_t status =
      fuchsia_hardware_input_DeviceGetBootProtocol(caller.borrow_channel(), &proto);
  caller.release().release();
  if ((status != ZX_OK) || (proto != fuchsia_hardware_input_BootProtocol_KBD)) {
    // skip devices that aren't keyboards
    close(fd);
    return ZX_ERR_NOT_SUPPORTED;
  }

  vc_input_t* vi;
  if ((status = vc_input_create(&vi, handler, fd)) < 0) {
    close(fd);
  }
  return status;
}
#endif
